// Refer: https://www.cnblogs.com/jssyjam/p/11910851.html

package gutils

import (
	"sync"

	"github.com/pkg/errors"
	cron "github.com/robfig/cron/v3"
)

// Crontab 定时任务管理器，提供对定时任务的添加、删除和管理功能。
//
// 它封装了底层的cron库，并提供了通过ID管理任务的能力。
type Crontab struct {
	inner *cron.Cron              // 内部cron实例
	ids   map[string]cron.EntryID // 任务ID到cron EntryID的映射
	mutex sync.Mutex              // 用于保护并发访问的互斥锁
}

// NewCrontab 创建一个新的定时任务管理器实例。
//
// 参数:
//   - secondsFieldLevel: 控制cron表达式中秒字段的处理方式：
//   - 0: 不包含秒字段（标准cron格式：分 时 日 月 周）
//   - 1: 必须包含秒字段（格式：秒 分 时 日 月 周）
//   - 2: 秒字段可选（支持两种格式）
//
// 返回值:
//   - *Crontab: 返回配置好的定时任务管理器实例
//
// 示例:
//
//	// 创建标准cron管理器（不带秒字段）
//	c := NewCrontab(0)
//	// 创建带必需秒字段的cron管理器
//	c := NewCrontab(1)
func NewCrontab(secondsFieldLevel uint) *Crontab {
	var c *cron.Cron

	switch secondsFieldLevel {
	case 0:
		c = cron.New()
	case 1:
		c = cron.New(cron.WithSeconds())
	case 2:
		cron.New(cron.WithParser(cron.NewParser(
			cron.SecondOptional | cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow | cron.Descriptor,
		)))
	}

	return &Crontab{
		inner: c,
		ids:   make(map[string]cron.EntryID),
	}
}

// IDs 返回所有有效的定时任务ID列表。
//
// 此方法会清理任何无效的任务ID，并返回当前有效的任务ID列表。
//
// 返回值:
//   - []string: 当前有效的任务ID列表
func (c *Crontab) IDs() []string {
	c.mutex.Lock()
	defer c.mutex.Unlock()
	validIDs := make([]string, 0, len(c.ids))
	invalidIDs := make([]string, 0)
	for sid, eid := range c.ids {
		if e := c.inner.Entry(eid); e.ID != eid {
			invalidIDs = append(invalidIDs, sid)
			continue
		}
		validIDs = append(validIDs, sid)
	}
	for _, id := range invalidIDs {
		delete(c.ids, id)
	}
	return validIDs
}

// Start 启动定时任务管理器，开始执行所有已添加的定时任务。
//
// 此方法会启动内部的cron实例，使所有已注册的任务开始按照其定义的时间表执行。
// 如果管理器已经在运行，此方法不会产生任何效果。
func (c *Crontab) Start() {
	c.inner.Start()
}

// Stop 停止定时任务管理器，暂停所有定时任务的执行。
//
// 此方法会停止内部的cron实例，所有已注册的任务将不再执行，直到再次调用Start方法。
// 已注册的任务不会被删除，只是暂停执行。
func (c *Crontab) Stop() {
	c.inner.Stop()
}

// DelByID 通过ID删除指定的定时任务。
//
// 参数:
//   - id: 要删除的任务的唯一标识符
//
// 如果指定ID的任务不存在，此方法不会产生任何效果。
// 删除后，该任务将不再执行，且其ID将从管理器中移除。
func (c *Crontab) DelByID(id string) {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	eid, ok := c.ids[id]
	if !ok {
		return
	}
	c.inner.Remove(eid)
	delete(c.ids, id)
}

// AddByID 添加一个带有唯一ID的定时任务。
//
// 参数:
//   - id: 任务的唯一标识符，用于后续管理该任务
//   - spec: cron表达式，定义任务的执行时间表
//   - cmd: 实现了cron.Job接口的任务对象
//
// 返回值:
//   - error: 如果ID已存在或cron表达式无效则返回错误，成功返回nil
//
// 示例:
//
//	err := crontab.AddByID("backup-job", "0 0 * * *", &BackupJob{})
func (c *Crontab) AddByID(id string, spec string, cmd cron.Job) error {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	if _, ok := c.ids[id]; ok {
		return errors.Errorf("crontab id exists")
	}
	eid, err := c.inner.AddJob(spec, cmd)
	if err != nil {
		return err
	}
	c.ids[id] = eid
	return nil
}

// AddByFunc 添加一个带有唯一ID的函数类型定时任务。
//
// 参数:
//   - id: 任务的唯一标识符，用于后续管理该任务
//   - spec: cron表达式，定义任务的执行时间表
//   - f: 要定时执行的函数
//
// 返回值:
//   - error: 如果ID已存在或cron表达式无效则返回错误，成功返回nil
//
// 示例:
//
//	err := crontab.AddByFunc("daily-cleanup", "0 0 * * *", func() {
//	    // 执行清理操作
//	})
func (c *Crontab) AddByFunc(id string, spec string, f func()) error {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	if _, ok := c.ids[id]; ok {
		return errors.Errorf("crontab id exists")
	}
	eid, err := c.inner.AddFunc(spec, f)
	if err != nil {
		return err
	}
	c.ids[id] = eid
	return nil
}

// IsExists 检查指定ID的定时任务是否存在。
//
// 参数:
//   - jid: 要检查的任务ID
//
// 返回值:
//   - bool: 如果任务存在返回true，否则返回false
//
// 示例:
//
//	if crontab.IsExists("backup-job") {
//	    // 任务已存在，执行相应操作
//	}
func (c *Crontab) IsExists(jid string) bool {
	_, exist := c.ids[jid]
	return exist
}
